﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DiscoveryLogic.Analytics.BusinessLogic.Clusters;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Measures;
using DiscoveryLogic.Analytics.DataObjects.Networks;
using DiscoveryLogic.Common.Utilities;

namespace DiscoveryLogic.Analytics.BusinessLogic.Measures
{
    public class NetworkMeasureWorker
    {
        private Network _Network;

        public NetworkMeasureWorker(Network network)
        {
            this._Network = network;
        }

        public NetworkMeasure GetNetworkMeasure()
        {
            NetworkMeasure netMeasure=new NetworkMeasure();
            netMeasure.NetworkName = this._Network.NetworkName;
            netMeasure.Density = 0;
            if (this._Network.Records.Count > 1)
                netMeasure.Density = (double) this._Network.Links.Count/
                                     (this._Network.Records.Count*(this._Network.Records.Count - 1.0)/2.0);

            // walk 
            Graph g = new Graph(this._Network);
            NetworkWalker walker = new NetworkWalker(g.Vertices.Values.ToList(), g.Edges.Values.ToList());
            netMeasure.Walks=new Dictionary<EntityBase, Dictionary<EntityBase, NetworkRoute>>();
            foreach(Vertice v in g.Vertices.Values)
            {
                EntityBase entity1 = v.Tag;
                Dictionary<Vertice, Route> toRoutes = walker.GetShortestPathsFrom(v);
                Dictionary<EntityBase, NetworkRoute> toEntityRoutes=new Dictionary<EntityBase, NetworkRoute>();
                foreach(Vertice v2 in toRoutes.Keys)
                {
                    if(v==v2)
                        continue;

                    Route route = toRoutes[v2];
                    EntityBase entity2 = v2.Tag;
                    List<Link> links=new List<Link>();
                    double distance = route.Distance;
                    int hops = 0;
                    foreach(Edge e in route.Edges)
                    {
                        hops++;
                        links.Add(e.Tag);
                    }
                    NetworkRoute netRoute = new NetworkRoute(entity1, entity2, links, distance, hops);
                    toEntityRoutes.Add(entity2, netRoute);
                    if(distance>netMeasure.EdgeDiameter)
                        netMeasure.EdgeDiameter = distance;
                    if(hops > netMeasure.NodeDiameter)
                        netMeasure.NodeDiameter = hops;
                }
                netMeasure.Walks.Add(entity1, toEntityRoutes);
            }
            
            // cluster
            NetworkPathClusterWorker clusterWorker=new NetworkPathClusterWorker(this._Network);
            clusterWorker.ClusterBasedOnCentrality(20);
            Dictionary<int, NetworkCluster> clusters = NetworkCluster.Read(this._Network.NetworkName);
            netMeasure.CommunityMeasures=new List<CommunityMeasure>();
            Dictionary<EntityType, Dictionary<string, EntityBase>> allEntities = this._Network.GetEntities();
            foreach(NetworkCluster cluster in clusters.Values)
            {
                CommunityMeasure community=new CommunityMeasure();
                community.CommunityID = cluster.ClusterID;
                community.Entities = cluster.ChildEntities;
                if(cluster.ChildEntities.Count>0)
                {
                    List<Link> insideLinks = Network.GetLinksWithinEntities(this._Network.Links, cluster.ChildEntities);
                    community.Density = 0;
                    if (cluster.ChildEntities.Count > 1)
                        community.Density =
                            (double) insideLinks.Count/
                            (cluster.ChildEntities.Count*(cluster.ChildEntities.Count - 1)/2.0);
                    community.Degree = 0;
                    community.InDegree = 0;
                    community.OutDegree = 0;

                    List<Link> adjacentLinks =
                        Network.GetLinksConnectedToEntities(this._Network.Links,
                                                            cluster.ChildEntities);
                    foreach(Link adjacentLink in adjacentLinks)
                    {
                        if(!insideLinks.Contains(adjacentLink))
                        {
                            if(adjacentLink.Directional)
                            {
                                EntityBase fromEntity =
                                    allEntities[EntityTypeUtil.GetEntityType(adjacentLink.FromType)][
                                        adjacentLink.FromKey];
                                EntityBase toEntity = allEntities[EntityTypeUtil.GetEntityType(adjacentLink.ToType)][
                                        adjacentLink.ToKey];
                                if (cluster.ChildEntities.Contains(fromEntity))
                                    community.OutDegree++;
                                else if (cluster.ChildEntities.Contains(toEntity))
                                    community.InDegree++;
                            }
                            community.Degree++;
                        }
                    }

                    int maxExternalDegrees = this._Network.Records.Count - community.Entities.Count;
                    double externalDensity = 1;
                    if (maxExternalDegrees > 0)
                    {
                        externalDensity = (double)community.Degree / maxExternalDegrees;
                    }
                    else
                        externalDensity = 1;
                    if (community.Density == 0)
                        community.Coefficient = 0;
                    else if (externalDensity == 0)
                        community.Coefficient = 0;
                    else
                        community.Coefficient = community.Density/externalDensity;
                }
                else
                {
                    community.Density = 0;
                    community.Degree = 0;
                    community.InDegree = 0;
                    community.OutDegree = 0;
                    community.Coefficient = 0;
                }
                
                netMeasure.CommunityMeasures.Add(community);
            }
            return netMeasure;
        }
    }
}
